/**
 * Implements the opensocial.Container apis.
 * @constructor
 */
opensocial.Hi5Container = function() {
  opensocial.Container.call(this);
};
opensocial.Hi5Container.inherits(opensocial.Container);

/**
 * Initialize the container with the specified state.
 *
 * @param {Person} viewer Person object that corresponds to the viewer.
 * @param {Person} opt_owner Person object that corresponds to the owner.
 * @param {Collection&lt;Person&gt;} opt_viewerFriends A collection of the
 *    viewer's friends
 * @param {Collection&lt;Person&gt;} opt_ownerFriends A collection of the
 *    owner's friends
 * @param {{Map&lt;String, String&gt;} opt_globalAppData map from key to value
 *    of the global app data
 * @param {{Map&lt;String, String&gt;} opt_instanceAppData map from key to value
 *    of this gadget's instance data
 * @param {Map&lt;Person, Map&lt;String, String&gt;&gt;} opt_personAppData map
 *    from person to a map of app data key value pairs.
 * @param {Array&lt;Activity&gt;} opt_activities An array of activity objects.
 */
 opensocial.Hi5Container.prototype.init = function(viewer,
                                               opt_owner,
                                               opt_viewerFriends,
                                               opt_ownerFriends,
                                               opt_globalAppData,
                                               opt_instanceAppData,
                                               opt_personAppData,
                                               opt_activities,
					       opt_appId) {
  this.viewer = viewer;
  this.owner = opt_owner;
  this.viewerFriends = opt_viewerFriends || {};
  this.ownerFriends = opt_ownerFriends || {};
  this.globalAppData = opt_globalAppData || {};
  this.instanceAppData = opt_instanceAppData || {};
  this.personAppData = opt_personAppData || {};
  this.activities = opt_activities || {};
  this.appId = opt_appId || 'sampleContainerAppId';
};


opensocial.Hi5Container.prototype.requestCreateActivity = function(activity,
    priority, opt_callback) {
  // Permissioning is not being handled in the mock container. All real
  // containers should check for user permission before posting activities.
  activity.setField(opensocial.Activity.Field.ID, 'postedActivityId');

  var userId = this.viewer.getId();
  var stream = activity.getField(opensocial.Activity.Field.STREAM);
  stream.setField(opensocial.Stream.Field.USER_ID, userId);
  stream.setField(opensocial.Stream.Field.APP_ID, this.appId);

  this.activities[userId] = this.activities[userId] || [];
  this.activities[userId].push(activity);

  if (opt_callback) {
    opt_callback();
  }
};


opensocial.Hi5Container.Mode = {CANVAS:"canvas", PROFILE:"profile", PREVIEW:"preview"};

opensocial.Hi5Container.prototype.navigateTo = function(mode, params) {

    switch(mode) {
      case opensocial.Hi5Container.Mode.CANVAS :
          window.location.href = "http://" + document.location.host + "/friend/displayAppCanvas.do?appName=Songs+iLike"
          break;
      case opensocial.Hi5Container.Mode.PROFILE :
          break;
      case opensocial.Hi5Container.Mode.PREVIEW :
          break;
    };
};

opensocial.Hi5Container.prototype.urlTo = function(mode, params) {
    var url;
    switch(mode) {
      case opensocial.Hi5Container.Mode.CANVAS :
          // TODO hack, fix for general case
          url = "/friend/displayAppCanvas.do?appName=Songs+iLike";
          alert(params);
          alert(params.length);
          break;
      case opensocial.Hi5Container.Mode.PROFILE :
          break;
      case opensocial.Hi5Container.Mode.PREVIEW :
          break;
    };
    return url;
};

/**
 * Deprecated.
 */
opensocial.Hi5Container.prototype.requestInstallApp = function(appUrl, opt_onSuccess, opt_onFailure) {
	// TODO implement
	opt_onSuccess();
};

/**
 * Deprecated.
 */
opensocial.Hi5Container.prototype.requestUninstallApp = function(appUrl, opt_onSuccess, opt_onFailure) {
	// TODO implement
	opt_onSuccess();
};

/**
 * Make the specified person as friend by moving them from the nonFriends
 * list to the friends list.
 *
 * @param {String} id The id associated with the person to be made a friend.
 * @param {Function} opt_onSuccess The callback method on successful completion.
 * @param {Function} opt_onFailure The callback method on failure.
 */
opensocial.Hi5Container.prototype.requestMakePersonAFriend = function(id, opt_onSuccess, opt_onFailure) {
  // Find if this id is in the list of non friends list
  var found = null;
  this.nonFriends.each(function(contact) {
    if(contact.getId() == id) {
      found = contact;
      this.nonFriends.removeAt(contact.getId());
    }
  });
  if(found == null) {
    opt_onFailure();
    return; // person not found in non friend list
  }

  // Make this person a friend by moving to friends collection and
  // deleting from non friend list
  this.friends.putAt(found.id, found);

  opt_onSuccess();
};

/**
 * Make the specified person as nonfriend by moving them from the friends
 * list to the nonFriends list.
 *
 * @param {String} id The id associated with the person to be removed
 * as a friend.
 * @param {Function} opt_onSuccess The callback method on successful completion.
 * @param {Function} opt_onFailure The callback method on failure.
 */
opensocial.Hi5Container.prototype.requestMakePersonNotAFriend = function(id,
    opt_onSuccess, opt_onFailure) {

  // Find if this person is actually a friend
  var found = null;
  this.friends.each(function(contact) {
    if(contact.getId() == id) {
      found = contact;
      this.friends.removeAt(contact.getId());
    }
  });
  if(found == null) {
    opt_onFailure();
    return; // person not found in friend list
  }

  // Add this person to non-friend list
  this.nonFriends.putAt(found.id, found);
  opt_onSuccess();
};


/**
 * This method returns the data requested about the viewer and his/her friends.
 * Since this is an in memory container, it is merely returning the member
 * variables. In a real world container, this would involve making an ajax
 * request to fetch the values from the server.
 *
 * To keep this simple (for now), the PeopleRequestFields values such as sort
 * order, filter, pagination, etc. specified in the DataRequest are ignored and
 * all requested data is returned in a single call back.
 *
 * @param {Object} dataRequest The object that specifies the data requested.
 * @param {Function} callback The callback method on completion.
 */
opensocial.Hi5Container.prototype.requestData = function(dataRequest, callback) {
  var requestObjects = dataRequest.getRequestObjects();
  var dataResponseValues = {};
  var globalError = false;
  
  // decrement this as we complete each request
  var numLeft = requestObjects.length;
	
  // This gets called when each request finishes
  var reqFinished = function(requestName, requestedValue) {
      dataResponseValues[requestName] = new opensocial.ResponseItem(request,requestedValue);
      numLeft--;
      if (numLeft == 0) {
	  callback(new opensocial.DataResponse(dataResponseValues, globalError));	
      }
  }
	
  // cycle through each element
  for (var requestNum = 0; requestNum < requestObjects.length; requestNum++) {
    var request = requestObjects[requestNum].request;
    var requestName = requestObjects[requestNum].key;
    var requestedValue;
    var hadError = false;

    switch(request.type) {
      case 'FETCH_PERSON' :
        var personId = request.id;

        if (personId == opensocial.DataRequest.PersonId.VIEWER) {
          requestedValue = this.viewer;
        } else if (personId == opensocial.DataRequest.PersonId.OWNER) {
          requestedValue = this.owner;
        } else {
          // See if the person is listed in an existing friends list
          if (requestedValue == null && this.viewerFriends != null) {
	      requestedValue = this.viewerFriends.get(personId);
          }
          if (requestedValue == null && this.ownerFriends != null) {
	      requestedValue = this.ownerFriends.get(personId);
          }
          if (requestedValue == null) {
	      // If we get here then the data is not locally cached, reach out and fetch it
	      // TODO add timeouts, error handline
	      jQuery.getJSON("http://api.hi5.com/json/profile/foaf/" + personId + "?callback=?", function(data){
		      person = data["rdf.RDF"]["foaf.Person"];
		      requestedValue = new people.Hi5Person(personId, {name: person["foaf.givenName"], picture:person["foaf.img"]["@resource"]}, false, false);
		      reqFinished(requestName, requestedValue);
		  });
          }
        }
        // This will be called in the non-ajax case.
        if (requestedValue != null)
	    reqFinished(requestName, requestedValue);
        break;

    case 'FETCH_PEOPLE' :
        var groupId = request.idSpec;
        
        var uid = null;
        if (groupId == opensocial.DataRequest.Group.VIEWER_FRIENDS) {
	    uid = this.viewer.getId();
        } else if (groupId == opensocial.DataRequest.Group.OWNER_FRIENDS) {
	    uid = this.owner.getId();
        } else {
	    // TODO unknown what we do here...
	    reqFinished(requestName, requestedValue);
        }

	// If we found a valid id fetch it
        if (uid != null) {          
	    jQuery.getJSON("http://api.hi5.com/json/profile/foaf/" + uid + "?callback=?", function(data){
		    knows = data["rdf.RDF"]["foaf.Person"]["foaf.knows"];
		    var friends = new Array();
		    
		    if (knows != null) {
			jQuery.each(knows, function () {
                        if (this["foaf.Person"]) {
				person = this["foaf.Person"];
                        } else if (this["foaf.gender"]) {
                                // gross, user has exactly one friend
                                person = this;
                        }
                                var seeAlso = null;

                                if (person == null)
				    return;

			        // <rdfs:seeAlso resource="http://api.hi5.com/rest/foaf/159987912"/>
				var id = person["foaf.mbox_sha1sum"];
				seeAlso = person["rdfs.seeAlso"]["@resource"];
				
				if (seeAlso) {
				    // ugh, hack the uid off the end
				    id = seeAlso.substring(seeAlso.lastIndexOf('/')+1);
				}
                                
				friends.push(new opensocial.Hi5Person(id, 
									       {name: person["foaf.givenName"],
										       picture: person["foaf.img"]["@resource"]}, false, false));
			    });
		    }
		    reqFinished(requestName, new opensocial.Collection(friends));
		});
        }  
        break;

    case 'FETCH_GLOBAL_APP_DATA' :
        var values = {};
        var keys =  request.keys;
        for (var i = 0; i < keys.length; i++) {
          values[keys[i]] = this.globalAppData[keys[i]];
        }
        requestedValue = values;

        reqFinished(requestName, requestedValue);
        
        break;

    case 'FETCH_INSTANCE_APP_DATA' :
        var values = {};
        var keys =  request.keys;
        for (var i = 0; i < keys.length; i++) {
          values[keys[i]] = this.instanceAppData[keys[i]];
        }
        requestedValue = values;
        reqFinished(requestName, requestedValue);        
        break;

    case 'UPDATE_INSTANCE_APP_DATA' :
        this.instanceAppData[request.key] = request.value;
        reqFinished(requestName, requestedValue);
        
        break;

    case 'FETCH_PERSON_APP_DATA' :
        var ids = this.getIds(request.idSpec);

        var values = {};
        for (var i = 0; i < ids.length; i++) {
          var id = ids[i];
          if (this.personAppData[id]) {
            values[id] = {};
            for (var j = 0; j < request.keys.length; j++) {
              values[id][request.keys[j]]
                  = this.personAppData[id][request.keys[j]];
            }
          }
        }
        requestedValue = values;
        reqFinished(requestName, requestedValue);
        
        break;

      case 'UPDATE_PERSON_APP_DATA' :
        var userId = request.id;
        // Gadgets can only edit viewer data
        if (userId == opensocial.DataRequest.PersonId.VIEWER
            || userId == this.viewer.getId()) {
          userId = this.viewer.getId();
          this.personAppData[userId] = this.personAppData[userId] || {};
          this.personAppData[userId][request.key] = request.value;
        } else {
          hadError = true;
        }

        reqFinished(requestName, requestedValue);
            
        break;

      case 'FETCH_ACTIVITIES' :
        var ids = this.getIds(request.idSpec);

        var requestedActivities = [];
        for (var i = 0; i < ids.length; i++) {
          requestedActivities
              = requestedActivities.concat(this.activities[ids]);
        }
        requestedValue = {
          // Real containers should set the requested stream here
          'requestedStream' : null,
          'activities' : new opensocial.Collection(requestedActivities)};

        reqFinished(requestName, requestedValue);
        
        break;
      default:
	reqFinished(requestName, null);

	break;
    }
  } // end of for loop
};

/**
 * Request a profile for the specified person id.
 * When processed, returns a Person object.
 *
 * @param {String} id The id of the person to fetch. Can also be standard
 *    person IDs of VIEWER and OWNER.
 * @param {Map&lt;opensocial.DataRequest.PeopleRequestFields, Object&gt;}
 *    opt_params Additional params to pass to the request. This request supports
 *    PROFILE_DETAILS.
 * @return {Object} a request object
 */
opensocial.Hi5Container.prototype.newFetchPersonRequest = function(id,
    opt_params) {
  return {'type' : 'FETCH_PERSON', 'id' : id};
};


/**
 * Used to request friends from the server, optionally joined with app data
 * and activity stream data.
 * When processed, returns a Collection&lt;Person&gt; object.
 *
 * @param {Array&lt;String&gt; or String} idSpec An id, array of ids, or a group
 *    reference used to specify which people to fetch
 * @param {Map&lt;opensocial.DataRequest.PeopleRequestFields, Object&gt;}
 *    opt_params Additional params to pass to the request. This request supports
 *    PROFILE_DETAILS, SORT_ORDER, FILTER, FIRST, and MAX.
 * @return {Object} a request object
 */
opensocial.Hi5Container.prototype.newFetchPeopleRequest = function(idSpec,
    opt_params) {
  return {'type' : 'FETCH_PEOPLE', 'idSpec' : idSpec};
};


/**
 * Used to request global app data.
 * When processed, returns a Map&lt;String, String&gt; object.
 *
 * @param {Array&lt;String&gt;|String} keys The keys you want data for. This
 *     can be an array of key names, a single key name, or "*" to mean
 *     "all keys".
 * @return {Object} a request object
 */
opensocial.Hi5Container.prototype.newFetchGlobalAppDataRequest = function(
    keys) {
  return {'type' : 'FETCH_GLOBAL_APP_DATA', 'keys' : keys};
};


/**
 * Used to request instance app data.
 * When processed, returns a Map&lt;String, String&gt; object.
 *
 * @param {Array&lt;String&gt;|String} keys The keys you want data for. This
 *     can be an array of key names, a single key name, or "*" to mean
 *     "all keys".
 * @return {Object} a request object
 */
opensocial.Hi5Container.prototype.newFetchInstanceAppDataRequest = function(
    keys) {
  return {'type' : 'FETCH_INSTANCE_APP_DATA', 'keys' : keys};
};


/**
 * Used to request an update of an app instance field from the server.
 * When processed, does not return any data.
 *
 * @param {String} key The name of the key
 * @param {String} value The value
 * @return {Object} a request object
 */
opensocial.Hi5Container.prototype.newUpdateInstanceAppDataRequest = function(
    key, value) {
  return {'type' : 'UPDATE_INSTANCE_APP_DATA', 'key' : key, 'value' : value};
};


/**
 * Used to request app data for the given people.
 * When processed, returns a Map&lt;person id, Map&lt;String, String&gt;&gt;
 * object.
 *
 * @param {Array&lt;String&gt; or String} idSpec An id, array of ids, or a group
 *    reference. (Right now the supported keys are VIEWER, OWNER,
 *    OWNER_FRIENDS, or a single id within one of those groups)
 * @param {Array&lt;String&gt;|String} keys The keys you want data for. This
 *     can be an array of key names, a single key name, or "*" to mean
 *     "all keys".
 * @return {Object} a request object
 */
opensocial.Hi5Container.prototype.newFetchPersonAppDataRequest = function(
    idSpec, keys) {
  return {'type' : 'FETCH_PERSON_APP_DATA', 'idSpec' : idSpec, 'keys' : keys};
};


/**
 * Used to request an update of an app field for the given person.
 * When processed, does not return any data.
 *
 * @param {String} id The id of the person to update. (Right now only the
 *    special VIEWER id is allowed.)
 * @param {String} key The name of the key
 * @param {String} value The value
 * @return {Object} a request object
 */
opensocial.Hi5Container.prototype.newUpdatePersonAppDataRequest = function(id,
    key, value) {
  return {'type' : 'UPDATE_PERSON_APP_DATA', 'id' : id, 'key' : key,
    'value' : value};
};


/**
 * Used to request an activity stream from the server.
 * Note: Although both app and folder are optional, you can not just provide a
 * folder.
 * When processed, returns an object where "activities" is a
 * Collection&lt;Activity&gt; object and "requestedStream" is a Stream object
 * representing the stream you fetched. (Note: this may or may not be different
 * that the streams that each activity belongs to)
 *
 * @param {Array&lt;String&gt; or String} idSpec An id, array of ids, or a group
 *  reference to fetch activities for
 * @param {Map&lt;opensocial.DataRequest.ActivityRequestFields, Object&gt;}
 *    opt_params Additional params to pass to the request.
 * @return {Object} a request object
 */
opensocial.Hi5Container.prototype.newFetchActivitiesRequest = function(idSpec,
    opt_params) {
  return {'type' : 'FETCH_ACTIVITIES', 'idSpec' : idSpec};
};
